﻿using Ionic.Zip;
using ServiceStack.Text;
using SolrNet.Attributes;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Mail;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace Application
{
    public class DatabaseManager
    {
        #region Fields
        private const string baseDirectoryPath = "c://TreeDb/";
        private string currentCursorPosition = "";
        private string dbName = "";
        private string tbName = "";
        private Guid currentDbUniqueKey = Guid.Empty;
        private DataBaseBST.BST<string, object> currTable = null;
        private TableManager table_manager;
        private CommandParser cp;
        private SolrManager<SolrEntity> man = new SolrManager<SolrEntity>();
        private Stopwatch sw;
        public event Action<object, TableEventArgs> GetTableEventHandler;

        #endregion

        #region Different Delegates
        private Action<IEnumerable<KeyValuePair<string,object>>> PrintToConsole = (collection) => {
            foreach(var it in collection)
                Console.WriteLine(it.Key + " --- > " + it.Value.ToJson());
            Console.WriteLine();
            Console.WriteLine();
           
        };

        private Action<string,TableManager,DataBaseBST.BST<string, object>,Action> UpdateDatabaseConfig = (path,manager,tree,upd) => {
            manager.FlushTable<string, object>(path, tree);
            upd();
        };

        public event Action<object, EventArgs> SuccessDatabaseCommandEventHandler;
        #endregion
        public DatabaseManager() { }

        private object locker = new object();

        public void setCommand(string command, out string consoleCursor, out DataBaseBST.BST<string, object> obj)
        {
            var methodStart = command.IndexOf('.') + 1;
            var main_command = command.Substring(0, methodStart - 1).Trim();
            switch (main_command)
            {
                case "db":
                    cp = new CommandParser(GrammaType.DatabasesAndTables);
                    cp.ErrorCommand += cp_ErrorCommand;
                    break;

                case "tbl":
                    cp = new CommandParser(GrammaType.DatabasesAndTables);
                    cp.ErrorCommand += cp_ErrorCommand;
                    break;

                case "rec":
                    cp = new CommandParser(GrammaType.Record);
                    cp.ErrorCommand += cp_ErrorCommand;
                    break;
            }

            if (cp.ifCorrect(command))
            {
                JsonObject json_obj;
                var methodEnd = command.IndexOf('=');

                var method = command.Substring(methodStart, methodEnd - methodStart);
                var lowest = command.IndexOf('(') + 1;
                var higest = command.IndexOf(')');
                var data_lowest = command.IndexOf(',');
                var data = data_lowest != -1 ? command.Substring(data_lowest + 1, higest - ++data_lowest) : "";
                var key = command.Substring(lowest, higest - lowest).Split(',')[0];
                string value = string.Empty;
                var first = command.IndexOf('{');
                var second = command.IndexOf('}') + 1;
                switch (main_command)
                {
                    case "db":

                        switch (method.Trim())
                        {
                            case "create":
                                Console.WriteLine(string.Format("Create Database [\"{0}\"]", key));
                                CreateDatabasesDirectories(key);
                                break;
                            case "drop":
                                DropDatabasesDirectories(key);
                                Console.WriteLine(string.Format("Droped Database [\"{0}\"]", key));
                                break;
                            case "get":

                                var res = GetDB(key);

                                if (res!= null)
                                {
                                    dbName = key;
                                    currentDbUniqueKey = res.DBUniqueKey;
                                }

                                break;
                            case "back":
                                CreateZIPVersion(string.Format("{0}{1}",baseDirectoryPath,key),"c:/solr",key);
                                break;
                            case "attach":
                                AttachDbFromZip(key);
                                break;
                            case "frw":
                                var pathToDb = CreateZIPVersion(string.Format("{0}{1}", baseDirectoryPath, key), "c:/solr", key);
                                SendDatabaseEmail(pathToDb,data);
                                break;
                        }
                        break;

                    case "tbl":
                        switch (method.Trim())
                        {
                            case "create":
                                table_manager = new TableManager();
                                table_manager.CreateTablesDirectories(currentCursorPosition, key);
                                Console.WriteLine(string.Format("Create Table [\"{0}\"]", key));
                                break;
                            case "drop":
                                table_manager = new TableManager(currentCursorPosition);
                                table_manager.DropDatabasesDirectories(key);
                                Console.WriteLine(string.Format("Droped Table [\"{0}\"]", key));
                                break;
                            case "get":

                                table_manager = new TableManager(currentCursorPosition);
                                var table = table_manager.GetTableDB(key);

                                if (table != null)
                                {
                                    tbName = key;
                                    currTable = table;
                                   // GetTableEventHandler(this, new TableEventArgs(dbName, tbName, table));
                                }
                                break;
                            case "save":
                                //table_manager = new TableManager(currentCursorPosition);
                                //table_manager.FlushTable<string, object>(string.Format("{0}{1}/{2}.treedb", currentCursorPosition, "tables", tbName), currTable);
                                //Console.WriteLine(string.Format("Saved Table [\"{0}\"]", key));
                                //UpdateConfigDatabaseFile();
                                #region AsyncUpdateAndSaveDBS
                                UpdateDatabaseConfig.BeginInvoke(string.Format("{0}{1}/{2}.treedb", currentCursorPosition, "tables", tbName), table_manager, currTable, UpdateConfigDatabaseFile, delegate { Console.WriteLine(); Console.WriteLine("Async delegate flushed"); }, null);
                                #endregion
                                break;
                        }
                        break;
                    case "rec":
                        switch (method.Trim())
                        {
                            case "add":
                                 json_obj = JsonObject.Parse(data);

                                
                            
                                     currTable.put(key, json_obj.ToArray());

                                     man.AddDocument(new SolrEntity()
                                     {
                                         DatabaseUniqueKey = currentDbUniqueKey.ToString(),
                                         Data = json_obj.Values,
                                         RecordKey = key,
                                         RecordId = Guid.NewGuid().ToString(),
                                         TableName = tbName,
                                         DatabaseName = dbName
                                     });
                             
                                
                                     //table_manager.FlushTable<string, object>(string.Format("{0}{1}/{2}.treedb", currentCursorPosition, "tables", tbName), currTable);
                                     #region AsyncUpdateAndSaveDBS
                                     UpdateDatabaseConfig.BeginInvoke(string.Format("{0}{1}/{2}.treedb", currentCursorPosition, "tables", tbName), table_manager, currTable,UpdateConfigDatabaseFile, delegate { Console.WriteLine(); Console.WriteLine("Async delegate flushed"); }, null);
                                    #endregion
                                break;

                            case "update":
                                json_obj = JsonObject.Parse(data);
                                currTable.update(key, json_obj.ToArray());
                                Console.WriteLine("Update command");
                                #region AsyncUpdateAndSaveDBS
                                UpdateDatabaseConfig.BeginInvoke(string.Format("{0}{1}/{2}.treedb", currentCursorPosition, "tables", tbName), table_manager, currTable, UpdateConfigDatabaseFile, delegate { Console.WriteLine(); Console.WriteLine("Async delegate flushed"); }, null);
                                #endregion
                                break;

                            case "read":
                                 sw = new Stopwatch();
                                 sw.Start();

                                if(key == "*")
                                  PrintToConsole(currTable.keys());
                             
                                Console.WriteLine("Read command");
                                Console.WriteLine(currTable.get(key).ToJson());
                                  Console.WriteLine();
                                  sw.Stop();
                               Console.WriteLine("Elapsed : " +sw.Elapsed);
                               Console.WriteLine();
                                break;

                            case "remove":
                                currTable.delete(key);
                                Console.WriteLine("Remove command");
                                #region AsyncUpdateAndSaveDBS
                                UpdateDatabaseConfig.BeginInvoke(string.Format("{0}{1}/{2}.treedb", currentCursorPosition, "tables", tbName), table_manager, currTable, UpdateConfigDatabaseFile, delegate { Console.WriteLine(); Console.WriteLine("Async delegate flushed"); }, null);
                                #endregion
                                break;

                            case "is":
                                Console.WriteLine(currTable.contains(key).ToString());
                                break;

                            case "glbsrc":
                                 sw = new Stopwatch();
                                sw.Start();
                                var globalSearchResult = man.SearchSolr("", "", key).Where(x => x.DatabaseUniqueKey == currentDbUniqueKey.ToString());
                                sw.Stop();
                                  Console.WriteLine();
                                Console.WriteLine("Global search result on query = " + key);
                                foreach (var iten in globalSearchResult)
                                {
                                    Console.WriteLine("DatabaseName : " + iten.DatabaseName);
                                    Console.WriteLine("TableName : " + iten.TableName);
                                    Console.WriteLine("Record key : "+ iten.RecordKey);
                                    Console.WriteLine("Record value :\n "+ currTable.get(iten.RecordKey).ToJson());
                                    
                                }
                               Console.WriteLine();
                               Console.WriteLine("Results Count = " + globalSearchResult.Count());
                               Console.WriteLine();
                               Console.WriteLine("Elapsed : " +sw.Elapsed);
                               Console.WriteLine();
                                break;
                        }
                        break;


                }
                Console.WriteLine();
                Console.BackgroundColor = ConsoleColor.Green;
                Console.ForegroundColor = ConsoleColor.Black;
                Console.WriteLine("Command successfully executed !");
                Console.ResetColor();
                Console.WriteLine();
            }
            else
            {
                Console.WriteLine();
                Console.BackgroundColor = ConsoleColor.Red;
                Console.ForegroundColor = ConsoleColor.Black;
                Console.WriteLine("Error parse command !");
                Console.ResetColor();
                Console.WriteLine();
            }

            if (tbName == string.Empty)
                consoleCursor = string.Format("{0}", dbName);
            else
                consoleCursor = string.Format("{0}.{1}", dbName, tbName);
            obj = currTable;
        }

        private void cp_ErrorCommand(object sender, ErrorCommandEventArgs e)
        {
            for(int i = 0; i < e.Command.Length; i++)
            {
                if (i == e.ErrorIndexCommand)
                {
                    Console.BackgroundColor = ConsoleColor.Red;
                    Console.ForegroundColor = ConsoleColor.Black;
                    Console.Write(e.Command[i]);
                    Console.ResetColor();
                    continue;
                }
                Console.Write(e.Command[i]);
            }
       
          
            Console.ResetColor();
        }

        #region DatabaseManager Core
        private bool CreateDatabasesDirectories(string dbDirectoryName)
        {
            if (!Directory.Exists(baseDirectoryPath))
                Directory.CreateDirectory(baseDirectoryPath);
            
            if (!Directory.Exists(string.Format("{0}{1}/", baseDirectoryPath, dbDirectoryName)))
            {
                Directory.CreateDirectory(string.Format("{0}{1}/", baseDirectoryPath, dbDirectoryName));
                File.WriteAllText(string.Format("{0}{1}/{2}", baseDirectoryPath, dbDirectoryName, "config.json"), ServiceStack.Text.JsonSerializer.SerializeToString<DBConfig>(new DBConfig(dbDirectoryName, GetDirectorySize(string.Format("{0}{1}/", baseDirectoryPath, dbDirectoryName)), 1, Environment.UserName, DateTime.UtcNow)));
                return true;
            }
            return false;

        }

        private bool DropDatabasesDirectories(string dbDirectoryName)
        {
            if (!Directory.Exists(baseDirectoryPath))
                return false;

            if (Directory.Exists(string.Format("{0}{1}/", baseDirectoryPath, dbDirectoryName)))
            {
                Directory.Delete(string.Format("{0}{1}/", baseDirectoryPath, dbDirectoryName), true);
                return true;
            }
            return false;
        }

        private DBConfig GetDB(string dbName)
        {
            if (dbName != "*")
            {
                currentCursorPosition = string.Format("{0}{1}/", baseDirectoryPath, dbName);
                if (Directory.Exists(currentCursorPosition))
                {
                    DBConfig config;
                    Console.WriteLine(string.Format("Database [ {0} ] are Successfully Executed  ", dbName));
                    Console.WriteLine();
                    Console.WriteLine(string.Format("Consfiguration : "));
                    using (StreamReader reader = new StreamReader(string.Format("{0}{1}/config.json", baseDirectoryPath, dbName)))
                    {
                        string data = "";
                        while (!reader.EndOfStream)
                        {
                            var line = reader.ReadLine();
                            data += line;
                            Console.WriteLine(string.Format("{0,-10}",line));
                        }
                         
                        config = JsonSerializer.DeserializeFromString<DBConfig>(data);
                    }
                    return config;
                }
                else
                {
                    Console.WriteLine("Database Exists !");
                    return null;
                }
            }
            else
            {
                foreach (var it in GetAllDbs(baseDirectoryPath))
                    Console.WriteLine(it);
                return new DBConfig() ;
            }

        }

        private long GetDirectorySize(string p)
        {
            string[] a = Directory.GetFiles(p, "*.*");
            long b = 0;
            foreach (string name in a)
                b += (new FileInfo(name)).Length;
            return b;
        }

        public IEnumerable<string> GetAllDbs(string baseDirectoryPath)
        {
            return Directory.GetDirectories(baseDirectoryPath)
                .Select(x => x.Substring(baseDirectoryPath.Length, x.Length - baseDirectoryPath.Length))
                .ToList();
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        public void UpdateConfigDatabaseFile()
        {
            TaskFactory tf = new TaskFactory();
            tf.StartNew(() =>
            {
                var files = Directory.GetFiles(currentCursorPosition, "*.*", SearchOption.AllDirectories);
                long size = 0L;
                int count = 0;

                foreach (var fileItem in files)
                {
                    var file = new FileInfo(fileItem);
                    size += file.Length;
                    count++;
                }

                DBConfig current_config;
                using (StreamReader reader = new StreamReader(string.Format("{0}/config.json", currentCursorPosition)))
                {
                    current_config = ServiceStack.Text.JsonSerializer.DeserializeFromString<DBConfig>(reader.ReadToEnd());
                    current_config.DBCountFiles = count;
                    current_config.DBSize = size;
                }
                File.WriteAllText(string.Format("{0}/config.json", currentCursorPosition), JsonSerializer.SerializeToString<DBConfig>(current_config));
            });
        }

        private string CreateZIPVersion(string sourceDbPath, string targetZipPath, string dbname)
        {
            if (!Directory.Exists("c://BackupJDT"))
                Directory.CreateDirectory("c://BackupJDT");
            ZipFile zip = new ZipFile();
            zip.AddDirectory(sourceDbPath);
            zip.Save(string.Format("{0}/{1}.zip","c://BackupJDT", dbname));

            return string.Format("{0}/{1}.zip", "c://BackupJDT", dbname);
        }

        private void AttachDbFromZip(string dbPath)
        {
            using (ZipFile zip = ZipFile.Read(dbPath))
            {
                var lst = zip.Name.LastIndexOf('/')+1;
                var name = zip.Name.WithoutExtension().Substring(lst, zip.Name.WithoutExtension().Length - lst);
                Directory.CreateDirectory(string.Format("{0}{1}", baseDirectoryPath, name));
                zip.ExtractAll(string.Format("{0}{1}", baseDirectoryPath, name));
            }
        }

        private void SendDatabaseEmail(string dbPath,string recipient)
        {
                SmtpClient client = new SmtpClient();
                client.Port = 587;
                client.Host = "smtp.gmail.com";
                client.Credentials = new System.Net.NetworkCredential("basket.pavlenko@gmail.com", "transcend123");
                client.EnableSsl = true;
                Attachment att = new Attachment(dbPath);
                att.Name = "JDT Database";
                MailMessage message = new MailMessage();
                message.Attachments.Add(att);
                message.From = new MailAddress("basket.pavlenko@gmail.com");
                message.To.Add(new MailAddress(recipient));
                message.Subject = "Database JDT";
                message.Body = "Show Attachments";
                client.SendAsync(message,null);
                client.SendCompleted += delegate { Console.WriteLine("Send Completed!"); };

        }
        #endregion

        #region EventsClasess
        public class TableEventArgs : EventArgs
        {
            public string DatabaseName { get; set; }
            public string TableName { get; set; }
            public DataBaseBST.BST<string, object> TableObject { get; set; }

            public TableEventArgs() { }

            public TableEventArgs(string dbName, string tbName, DataBaseBST.BST<string,object> treeObject)
            {
                DatabaseName = dbName;
                TableName = tbName;
                TableObject = treeObject;
            }
        }

        public class DatabaseEventArgs : EventArgs
        {
            public string DatabaseName { get; set; }
       
        
            public DatabaseEventArgs() { }

            public DatabaseEventArgs(string dbName)
            {
                DatabaseName = dbName;
          
            }
        }
        #endregion
    }
}
